import {showAccessTokenExpiredV2, showInternalErrorV2} from "@/utils/requestHelper";
import globalData from "@/global_data";
export interface ServerResponse {
  desc: string;
  code: string,
  header: any,
  data?: any,
  list?: any,
}
export enum RequestExceptionType {
  NO_DATA_ERROR = 'NO_DATA_ERROR', //没有响应体
  TOKEN_EXPIRED_ERROR = 'TOKEN_EXPIRED_ERROR', //token过期
  TOKEN_NONE_ERROR = 'TOKEN_NONE_ERROR', //token不存在
  BUSINESS_ERROR = 'BUSINESS_ERROR', //返回业务错误(code !== '1')
  HTTP_STATUS_CODE_ERROR = 'HTTP_STATUS_CODE_ERROR', //http请求状态码错误
  REQUEST_TIMEOUT = 'REQUEST_TIMEOUT', //请求超时
  FAIL = 'REQUEST_FAIL',
  MAX_REQUEST = 'MAX_REQUEST', //超过最大请求次数
  NO_NETWORK = 'NO_NETWORK', //网络断开
  HAND_UP = 'HAND_UP', //服务被挂起
}

enum CommonStatusErrorCodeToTip {
  _400 = "版本低，请删除小程序重试~",
  _404 = "版本不匹配，请删除小程序重试~",
  _500 = "加载错误，请稍后重试~",
}

//构造501~505的错误码
for (let i = 501; i <= 505; i++) {
  CommonStatusErrorCodeToTip[`_${i}`] = "加载错误，请稍后重试~";
}

export interface IRequestException {
  type: RequestExceptionType,
  content?: string
}

class RequestException implements IRequestException {
  content?: string;
  type: RequestExceptionType;

  constructor(type: RequestExceptionType, content?: string) {
    this.type = type;
    this.content = content;
  }
}

export interface RequestOptions {
  url: string,                                  //url后缀|url全路径
  businessErrorCode?: string,                   //业务错误码: '错误码'(描述为错误回调信息)
  internalErrorCode?: string,                   //内部错误码: '错误码'(描述为错误回调信息)
  businessErrorExcept?: string[],               //当请求成功，除了'1'外哪些code是不需要提示错误的 ['code1', 'code2', ... , 'codeN']
  data?: object,                                //http请求参数
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE',   //请求方法
  accessToken?: string,                         //accessToken(可自定义)
  pureData?: boolean,                           //是否只获取后端返回的data字段内容，默认true
  showErrorTip?: boolean                        //是否显示错误提示弹窗
  timeoutMills?: number                         //超时时间
}

const defaultRequestOption: RequestOptions = {
  url: '',
  businessErrorExcept: [],
  data: {},
  method: 'GET',
  accessToken: '',
  pureData: true,
  showErrorTip: true,
  timeoutMills: 60 * 1000,
}


const handleNot200 = (statusCode: number, options, reject, header) => {
  //TODO: 考虑可单独可提出的常见错误码
  if (options.showErrorTip) showInternalErrorV2(options.modal, {
    content: CommonStatusErrorCodeToTip[`_${statusCode}`] || '网络繁忙，请稍后重试'
  });
  return reject(new RequestException(RequestExceptionType.HTTP_STATUS_CODE_ERROR, CommonStatusErrorCodeToTip[`_${statusCode}`] || '网络繁忙，请稍后重试'));
}

const successRequestFunc = (res: any,
                            options: RequestOptions,
                            header: any,
                            resolve: (value: ServerResponse | any) => void,
                            reject: (reason: RequestException) => void) => {

  if (res.statusCode !== 200) return handleNot200(res.statusCode, options, reject, header);

  if (!res.data) {
    if (options.showErrorTip) showInternalErrorV2();
    return reject(new RequestException(RequestExceptionType.NO_DATA_ERROR));
  }
  if (res.data.code === '109') {
    if (options.showErrorTip) showAccessTokenExpiredV2();
    return reject(new RequestException(RequestExceptionType.TOKEN_EXPIRED_ERROR));
  }
  if (res.data.code === '105') {
    if (options.showErrorTip) showAccessTokenExpiredV2();
    return reject(new RequestException(RequestExceptionType.TOKEN_NONE_ERROR));
  }
  if (options.businessErrorExcept!.concat(["1"]).findIndex(item => item === res.data.code) === -1) {
    if (options.showErrorTip) showInternalErrorV2({
      content: res.data.desc,
    });
    return reject(new RequestException(RequestExceptionType.BUSINESS_ERROR, res.data.desc || '请稍后重试'));
  }
  res.data.header = res.header;
  if (options.pureData) {
    return resolve(res.data.data);
  }
  return resolve(res.data);
}

const showErrFunc = (content, options: RequestOptions) => {
  if (options.showErrorTip) showInternalErrorV2({
    content,
  });
}

const failRequestFunc = (f, options: RequestOptions, header: any, reject: (reason: RequestException) => void) => {
  //当success回调函数内部发生错误，fail回调函数不会被调用
  console.log(f)

  /**
   * 区分不同的错误类型
   * 处理支付宝非200状态码，此时f?.error为19
   * f?.error：14(未测出，但支付宝文档注明)
   * */

  //服务被挂起，网络被迫中断，支付宝暂时无法判断
  if ((f?.errno === 600003 || f?.errCode === 600003)) {
    const errTip = '请求中断，请重试~';
    showErrFunc(errTip, options);
    return reject(new RequestException(RequestExceptionType.HAND_UP, errTip));
  }

  //超过最大请求次数，支付宝暂时无法判断
  if (f?.errno === 600007 || f?.errCode === 600007) {
    const errTip = '当前网络繁忙，请稍后重试~';
    showErrFunc(errTip, options);
    return reject(new RequestException(RequestExceptionType.MAX_REQUEST, errTip));
  }

  /**
   * 用户网络断开，errno：安卓，errorCode：iOS
   * 其中在ios上，f存在netErrorCode属性，值为-1009。安卓上不存在netErrorCode属性
   * 109：断网之后，立即发起请求出现109
   * 105：断网之后，先发起请求触发了109，然后点击返回再进入页面，再次发起请求，大约1分钟之后出现105
   * 支付宝在出现f?.error为12时，无法准确判断网络是否断开，还是未完成的操作亦或是超时，因为支付宝在安卓手机不存咋netErrorCode属性
   * */
  if (f?.error === 12 || f?.errno === 600001 || f?.errCode === 600001) {
    const errTip = '网络断开，请恢复网络后重试~';
    showErrFunc(errTip, options);
    return reject(new RequestException(RequestExceptionType.NO_NETWORK, errTip));
  }

  /**
   * 可能两种情况：
   * 1.用户网络异常
   * 2.服务器正在发生异常
   * */
  // @ts-ignore
  if (f?.error === 13
    || f?.errMsg?.includes('timeout')
    || f?.errMsg?.includes('time out')
    || f?.errMsg?.includes('TIMED_OUT')) {
    const errTip = '获取数据超时，请重启网络后重试~';
    showErrFunc(errTip, options);
    return reject(new RequestException(RequestExceptionType.REQUEST_TIMEOUT, errTip));
  }
  const errTip = '获取数据失败，请稍后重试~';
  //兜底逻辑
  showErrFunc(errTip, options);
  return reject(new RequestException(RequestExceptionType.FAIL, errTip));
}

export const request = (options: RequestOptions): Promise<ServerResponse | any> => {
  options = Object.assign({}, defaultRequestOption, options);
  const header = {'content-type': 'application/json', 'clientType': globalData.platform === 'alipay' ? 'MY' : 'MP'};
  header['accessToken'] = options.accessToken || globalData.accessToken;
  if (globalData.user?.id) header['uid'] = globalData.user.id;
  return new Promise((resolve, reject: (reason: RequestException) => void) => {
    Taro.request({
      url: options.url,
      data: options.data,
      method: options.method,
      header,
      timeout: options.timeoutMills,
      referrerStrategy: 'index',
      success: function (res) {
        successRequestFunc(res, options, header, resolve, reject);
      },
      fail: function (f) {
        failRequestFunc(f, options, header, reject);
      }
    })
  });
}

